/*
 * @Author: 13177709051 3506463075@qq.com
 * @Date: 2024-10-23 00:07:51
 * @LastEditors: 13177709051 3506463075@qq.com
 * @LastEditTime: 2024-10-23 17:38:24
 * @FilePath: /linux21/ProcessPool/ProcessPool.cc
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */

#include <iostream>      // 标准输入输出流
#include <string>       // 字符串处理
#include <vector>       // 向量（动态数组）
#include <unistd.h>     // UNIX 标准函数
#include <sys/types.h>  // 数据类型定义
#include <sys/wait.h>   // 进程等待
#include "Task.hpp"     // 自定义任务头文件，假设其中定义了任务相关的函数和类型


///Channel类模块
class Channel
{
public:
    Channel(int wfd, pid_t id, const std::string& name)
        :_wfd(wfd), _subprocessid(id), _name(name)
    {
    }
    int GetWfd() {return _wfd;}
    pid_t GetProcessId() {return _subprocessid;}
    std::string GetName() {return _name;}
    void CloseChannel()
    {
        close(_wfd);
    }
    void Wait()
    {
        pid_t rid = waitpid(_subprocessid, nullptr, 0);
        if(rid > 0)
        {
            std::cout << "wait " << rid << "success" << std::endl;
        }
    }
    ~Channel()
    {
    }

private:
    int _wfd;
    pid_t _subprocessid;
    std::string _name;
};


//形参类型和命名规范
// const & : 输出
// & : 输入输出性参数
// * : 输出性参数
// task_t task : 回调函数


//进程与管道创建模块
void CreateChannelAndSub(int num, std::vector<Channel> *channels, task_t task)
{
    for(int i = 0; i < num; i++)
    {
        //1. 创建管道
        int pipefd[2] = {0};
        int n = pipe(pipefd);
        if(n < 0) exit(1);

        //2. 创建子进程
        pid_t id = fork();
        if(id == 0)
        {
            if(!channels->empty())
            {
                //第二次之后, 开始创建新的管道
                for(auto &channel : *channels) channel.CloseChannel();
            }
            //child - read
            close(pipefd[1]);
            dup2(pipefd[0], 0); //将管道的读端重定向到标准输入
            task();
            close(pipefd[0]);
            exit(0);
        }

        //3. 构建一个channel名称
        std::string channel_name = "Channel-" + std::to_string(i);
        //父进程
        close(pipefd[0]);
        //a. 子进程的pid b. 父进程关心的管道的w端
        channels->push_back(Channel(pipefd[1], id, channel_name)); 
    }
}



// 参数
// int num:
//
// 要创建的子进程数量。
// std::vector<Channel> *channels:
//
// 指向 Channel 对象的向量，用于存储每个子进程及其对应的管道信息。
// task_t task:
//
// 传入的任务函数指针，子进程执行的任务。
// 函数逻辑
// 创建管道:
//
// 使用 pipe(pipefd) 创建一个管道，pipefd[0] 为读端，pipefd[1] 为写端。若创建失败，调用 exit(1) 终止程序。
// 创建子进程:
//
// 使用 fork() 创建子进程。返回值 id:
// 如果 id 为 0，表示当前进程为子进程。
// 子进程执行以下操作：
// 如果 channels 不为空，关闭已存在的管道的写端，确保没有文件描述符泄露。
// 关闭管道的写端 (close(pipefd[1]))，并使用 dup2(pipefd[0], 0) 将管道的读端重定向到标准输入，以便子进程可以从管道读取数据。
// 执行传入的 task() 函数。
// 关闭读端并退出。
// 父进程操作:
//
// 父进程关闭管道的读端 (close(pipefd[0]))，因为只关心写端。
// 创建一个通道名称，如 "Channel-0"，然后将子进程的 PID 和写端的文件描述符封装到 Channel 对象中，并将其添加到 channels 向量中。
// 总体分析
// 这个函数高效地管理了多进程创建与通信。它确保每个子进程拥有独立的管道，并且在创建新的管道前关闭不再使用的管道，避免资源泄露。task 函数的执行允许用户定义子进程的具体工作，实现灵活的任务分配。这种结构适合需要并行处理的场景，如数据处理或并发计算。



//0 1 2 3 4 channelnum
int NextChannel(int channelnum)
{
    static int next = 0;
    int channel = next;
    next++;
    next %= channelnum;
    return channel;
}

//任务分配与执行控制模块
void SendTaskCommand(Channel &Channel, int taskcommand)
{
    write(Channel.GetWfd(), &taskcommand, sizeof(taskcommand));
}

void ctrlProcessOnce(std::vector<Channel> &Channels)
{
    sleep(1);
    //1. 选择一个任务
    int taskcommand = SelectTask();
    //2. 选择一个信道和进程
    int channel_index = NextChannel(Channels.size());
    //3. 发送任务
    SendTaskCommand(Channels[channel_index], taskcommand);
    std::cout << std::endl;
    std::cout << "taskcommand: " << taskcommand << " channel: " << Channels[channel_index].GetName() << " sub process: " << Channels[channel_index].GetProcessId() << std::endl;
}


void ctrlProcess(std::vector<Channel> &channels, int times = -1)
{
    if(times > 0)
    {
        while(times--)
        {
            ctrlProcessOnce(channels);
        }
    }
    else
    {
        while(true)
        {
            ctrlProcessOnce(channels);
        }
    }
} 


// 1. NextChannel 函数
// 功能:循环返回下一个信道的索引。
// 实现:
// 使用静态变量 next 来保存当前的信道索引。
// 每次调用时返回当前的 next 值，并将其增加，随后使用取模操作确保它不会超过 channelnum - 1。
// 特点:保证信道的选择是循环的，这样可以平均分配任务到各个信道上。
// 2. SendTaskCommand 函数
// 功能:发送任务命令到指定的信道。
// 实现:
// 使用 write 函数将 taskcommand 的值写入信道的写端。
// Channel.GetWfd() 获取信道的写文件描述符（WFD），确保命令能够被子进程接收。
// 特点:该函数直接进行系统调用以发送数据.
// 3. ctrlProcessOnce 函数
// 功能:控制单次任务分配和发送。
// 实现:
// 首先调用 sleep(1) 暂停执行，以控制任务发送的频率。
// 使用 SelectTask() 选择一个任务命令。
// 调用 NextChannel 获取下一个信道的索引。
// 发送选定的任务到对应信道。
// 打印发送的任务信息，包括任务命令、信道名称和子进程 ID。
// 4. ctrlProcess 函数
// 功能:控制多个任务的发送。
// 实现:
// 接受一个可选的参数 times，用于指定任务发送的次数。
// 如果 times 大于 0，则执行 times 次任务发送；否则，进入无限循环，不断发送任务。
// 特点:通过这个函数，用户可以灵活控制任务的发送次数，适应不同的需求场景。

//5. 清理与资源回收模块

void CleanUpChannel(std::vector<Channel> &channels)
{
    for(auto &channels : channels)
    {
        channels.CloseChannel();
        channels.Wait();
    }
}


// Wait() 会调用操作系统的 wait 系统调用，确保主进程在子进程结束之前不会继续执行，从而避免僵尸进程。

//主函数模块
//处理命令行参数和程序的主逻辑。

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cerr << "Usage: " << argv[0] << " processnum" << std::endl;
        return 1;
    }
    int num = std::stoi(argv[1]);
    LoadTask();

    std::vector<Channel> channels;
    // 1. 创建信道和子进程
    CreateChannelAndSub(num, &channels, work1);

    // 2. 通过channel控制子进程
    ctrlProcess(channels, 5);

    // 3. 回收管道和子进程. a. 关闭所有的写端 b. 回收子进程
    CleanUpChannel(channels);

    // sleep(100);
    return 0;
}

// int main(int argc, char *argv[]): 这是程序的入口点。argc 表示命令行参数的数量，argv 是一个字符串数组，存储所有参数。
// if (argc != 2): 这个条件检查传入的参数数量是否等于 2。第一个参数（argv[0]）是程序本身的名称，第二个参数（argv[1]）应该是用户提供的。
// 功能: 将输入的参数（子进程数量）转换为整数类型，存储在 num 变量中。
// 功能: 调用 LoadTask() 函数，通常用于加载一些必要的资源或设置

